<?php
/**
*	@package UHU_Components
*	@subpackage PDO_doc_cache
**/

/**
*	@package UHU_exception
*	@subpackage PDO_doc_cache
**/
class PDO_doc_cache_Exception extends uhu_exception
{
	protected function getExtendedMessage($code=null)
	{
		$message = '%s';
		switch($code)
		{
			case 100: # PDO::__construct in PDO_doc_cache::PDO
			case 101: # create table in PDO_doc_cache::PDO 
			case 102: # create index in PDO_doc_cache::PDO
				$message = 'Could not build PDO object: %s';
			break;
			
			case 103:
			case 104:
			case 105:
				$message = 'Invalid arguments: %s';
			break;

			case 200: # PDO::prepare
			case 201: # PDOStatement::bindParam
			case 202: # PDOStatement::bindColumn
			case 203: # PDOStatement::execute
			case 204: # PDOStatement::fetch(PDO::FETCH_BOUND);
				$message = 'Could not check database to see if document was cached: %s'; # PDO_doc_cache::is_cached
			break;

			case 300: # PDO::prepare
			case 301: # PDOStatement::bindParam
			case 302: # PDOStatement::bindColumn
			case 303: # PDOStatement::execute
			case 304: # PDOStatement::fetch(PDO::FETCH_BOUND);
				$message = 'Could not fetch document from database: %s'; # PDO_doc_cache::fetch
			break;

			case 400: # PDO::prepare
			case 401: # PDOStatement::bindParam
			case 402: # PDOStatement::bindColumn
			case 403: # PDOStatement::execute
			case 404: # PDOStatement::fetch(PDO::FETCH_BOUND);
				$message = 'Could not cache document in database: %s'; # PDO_doc_cache::cache
			break;

			case 500: # PDO::prepare
			case 501: # PDOStatement::bindParam
			case 502: # PDOStatement::bindColumn
			case 503: # PDOStatement::execute
			case 504: # PDOStatement::fetch(PDO::FETCH_BOUND);
				$message = 'Could not run garbage collection on cache: %s'; # PDO_doc_cache::gc_cache
			break;

			case 600: # PDO::prepare
			case 601: # PDOStatement::bindParam
			case 602: # PDOStatement::bindColumn
			case 603: # PDOStatement::execute
			case 604: # PDOStatement::fetch(PDO::FETCH_BOUND);
				$message = 'Could not clear all cache: %s'; # PDO_doc_cache::clear_all_cache
			break;

			case 700: # PDO::prepare
			case 701: # PDOStatement::bindParam
			case 702: # PDOStatement::bindColumn
			case 703: # PDOStatement::execute
			case 704: # PDOStatement::fetch(PDO::FETCH_BOUND);
				$message = 'Could not clear cache: %s'; # PDO_doc_cache::clear_cache
			break;
		}
		return $message;
	}
}

/**
*	{@link http://smarty.net/ Smarty}-inspired caching mechanism designed to be used with {@link http://uk.php.net/PDO PHP Data Object} interfaces such as SQLite
*	@package PDO_doc_cache
**/
class PDO_doc_cache
{
/**
*	@var PDO Holds the PDO object used to interact with the database
*/
	protected $PDO;
/**
*	@var bool When set to TRUE all data is autoamtically serialize prior to storage, and unserialized during the fetching process
*/
	protected $auto_serialize = false;
/**
*	@var callback|null Useful for specifying functions with which to decompress the data prior to being returned
*/
	protected $fetch_callback;
/**
*	@var callback|null Useful for specifying functions with which to compress the data prior to being stored
*/
	protected $cache_callback;

/**
*	@var PDOStatement holds the statement handler for retrieving the document
*/
	protected $sth_get_document;
/**
*	@var PDOStatement holds the statement handler for retrieving the document expiry time
*/
	protected $sth_get_expires;
/**
*	@var PDOStatement holds the statement handler for retrieving the document creation time
*/
	protected $sth_get_created;
/**
*	@var PDOStatement holds the statement handler for storing the document
*/
	protected $sth_cache_document;
/**
*	@var PDOStatement holds the statement handler for runnig garbage collection on the cache
*/
	protected $sth_gc_cache;
/**
*	@var PDOStatement holds the statement handler for clearing all cache entries
*/
	protected $sth_clear_all_cache;
/**
*	@var PDOStatement holds the statement handler for clearing all cache entries with a specific filename
*/
	protected $sth_clear_cache_filename;
/**
*	@var PDOStatement holds the statement handler for clearing all cache entries with a specific filename and cache id
*/
	protected $sth_clear_cache_filename_cache_id;
/**
*	@var PDOStatement holds the statement handler for clearing all cache entries with a specific filename, cache id and compile id
*/
	protected $sth_clear_cache_filename_cache_id_compile_id;
/**
*	@var PDOStatement holds the statement handler for clearing all cache entries with a specific filename and compile id
*/
	protected $sth_clear_cache_filename_compile_id;
/**
*	@var PDOStatement holds the statement handler for clearing all cache entries with a specific cache id
*/
	protected $sth_clear_cache_cache_id;
/**
*	@var PDOStatement holds the statement handler for clearing all cache entries with a specific compile id
*/
	protected $sth_clear_cache_compile_id;
/**
*	@var PDOStatement holds the statement handler for clearing all cache entries with a specific cache id and compile id
*/
	protected $sth_clear_cache_cache_id_compile_id;

/**
*	string SQL query used to create the PDO_doc_cache table
*/
	const sql_create_table =
'CREATE TABLE IF NOT EXISTS PDO_doc_cache (
	id CHAR(40) PRIMARY KEY NOT NULL,
	filename CHAR(255) NOT NULL,
	cache_id CHAR(255) NOT NULL,
	compile_id CHAR(255) NOT NULL,
	created INT(10) NOT NULL,
	lifetime INT(8) NOT NULL,
	document BLOB
)';
/**
*	string SQL query used to create the index for PDO_doc_cache
*/
	const sql_create_index =
'CREATE UNIQUE INDEX IF NOT EXISTS
	filename
ON
	PDO_doc_cache (filename,cache_id,compile_id)';
/**
*	string SQL query used to retrieve the document
*/
	const sql_get_document =
'SELECT
	document
FROM
	PDO_doc_cache
WHERE
	filename=:filename AND
	cache_id=:cache_id AND
	compile_id=:compile_id';
/**
*	string SQL query used to retrieve the document expiry time
*/
	const sql_get_expires =
'SELECT
	(created + lifetime) AS expires
FROM
	PDO_doc_cache
WHERE
	filename=:filename AND
	cache_id=:cache_id AND
	compile_id=:compile_id';
/**
*	string SQL query used to retrieve the document creation time
*/
	const sql_get_created =
'SELECT
	created
FROM
	PDO_doc_cache
WHERE
	filename=:filename AND
	cache_id=:cache_id AND
	compile_id=:compile_id';
/**
*	string SQL query used to store the document
*/
	const sql_cache_document =
'INSERT OR REPLACE INTO
	PDO_doc_cache (id,filename,cache_id,compile_id,created,lifetime,document)
VALUES(
	:id,
	:filename,
	:cache_id,
	:compile_id,
	:created,
	:lifetime,
	:document
)';
/**
*	string SQL query used for runnig garbage collection on the cache
*/
	const sql_gc_cache =
'DELETE FROM
	PDO_doc_cache
WHERE
	(created + lifetime) < (:now + 0)';
/**
*	string SQL query used for clearing all cache entries
*/
	const sql_clear_all_cache =
'DELETE FROM
	PDO_doc_cache
WHERE
	(:now - created) > :expire_time';
/**
*	string SQL query used for clearing all cache entries with a specific filename
*/
	const sql_clear_cache_filename =
'DELETE FROM
	PDO_doc_cache
WHERE
	filename=:filename';
/**
*	string SQL query used for clearing all cache entries with a specific filename and cache id
*/
	const sql_clear_cache_filename_cache_id =
'DELETE FROM
	PDO_doc_cache
WHERE
	filename=:filename AND
	cache_id=:cache_id';
/**
*	string SQL query used for clearing all cache entries with a specific filename, cache id and compile id
*/
	const sql_clear_cache_filename_cache_id_compile_id =
'DELETE FROM
	PDO_doc_cache
WHERE
	filename=:filename AND
	cache_id=:cache_id AND
	compile_id=:compile_id AND
	(:now - created) >= (:expire_time + 0)';
/**
*	string SQL query used for clearing all cache entries with a specific filename and compile id
*/
	const sql_clear_cache_filename_compile_id =
'DELETE FROM
	PDO_doc_cache
WHERE
	filename=:filename AND
	compile_id=:compile_id
	(:now - created) >= (:expire_time + 0)';
/**
*	string SQL query used for clearing all cache entries with a specific cache id
*/
	const sql_clear_cache_cache_id =
'DELETE	FROM
	PDO_doc_cache
WHERE
	cache_id=:cache_id
	(:now - created) >= (:expire_time + 0)';
/**
*	string SQL query used for clearing all cache entries with a specific compile id
*/
	const sql_clear_cache_compile_id =
'DELETE FROM
	PDO_doc_cache
WHERE
	compile_id=:compile_id
	(:now - created) >= (:expire_time + 0)';
/**
*	string SQL query used for clearing all cache entries with a specific cache id and compile id
*/
	const sql_clear_cache_cache_id_compile_id =
'DELETE FROM
	PDO_doc_cache
WHERE
	cache_id=:cache_id AND
	compile_id=:compile_id
	(:now - created) >= (:expire_time + 0)';

/**
*	Error message thrown when an SQL query could not be prepared
*/
	const error_string_PDO_could_not_prepare_statement = 'Could not prepare statement.';
/**
*	Error message thrown when parameters/values could not be bound to a prepared statement
*/
	const error_string_PDO_could_not_bind_parameters   = 'Could not bind parameters to prepared statement.';
/**
*	Error message thrown when a column from a prepared statement could not be bound to a variable
*/
	const error_string_PDO_could_not_bind_column       = 'Could not bind column to value on prepared statement.';
/**
*	Error message thrown when a prepared statement could not be executed
*/
	const error_string_PDO_could_not_execute_statement = 'Could not execute prepared statement.';
/**
*	Error message thrown when a bound column could not be fetched
*/
	const error_string_PDO_could_not_fetch_bound_col   = 'Could not fetch bound column.';

/**
*	@param PDO $PDO the PDO object used to interact with the database
*	@uses PDO_doc_cache::$PDO
*	@uses PDO_doc_cache::gc_cache()
*/
	public function __construct(PDO $PDO)
	{
		$this->PDO = $PDO;
		$this->gc_cache();
	}
/**
*	Resets the PDO_doc_cache clone to safe defaults
*	@uses PDO_doc_cache::$auto_serialize
*	@uses PDO_doc_cache::$fetch_callback
*	@uses PDO_doc_cache::$cache_callback
*/
	public function __clone()
	{
		$this->auto_serialize = false;
		$this->fetch_callback = null;
		$this->cache_callback = null;
	}

/**
*	Creates a PDO object intended to be used with {@link PDO_doc_cache::__construct}
*	@param string $dsn the {@link http://en.wikipedia.org/wiki/Database_Source_Name DSN} used to connect to the database
*	@link http://uk.php.net/PDO
*	@return PDO
*	@uses PDO_doc_cache_Exception
*/
	public static function PDO($dsn,$user=null,$pass=null,array $driver_options=null)
	{
		try
		{
			$PDO = new PDO($dsn,$user,$pass,$driver_options);
		}
		catch(PDOException $e)
		{
			throw new PDO_doc_cache_Exception('Could not establish database connection.',100);
		}
		$create_table = true;
		try
		{
			$create_table = $PDO->exec(self::sql_create_table);
		}
		catch(PDOException $e)
		{
			$create_table = false;
		}
		if($create_table === false)
		{
			throw new PDO_doc_cache_Exception('Could not create PDO_doc_cache table.',101);
		}
		$create_index = true;
		try
		{
			$create_index = $PDO->exec(self::sql_create_index);
		}
		catch(PDOException $e)
		{
			$create_index = false;
		}
		if($create_index === false)
		{
			throw new PDO_doc_cache_Exception('Could not create indexes on PDO_doc_cache table.',102);
		}
		return $PDO;
	}
/**
*	Returns a uniqie identifier intended to be used as the PRIMARY KEY in a table
*	@return string
*/
	public static function row_id($filename,$cache_id='',$compile_id='')
	{
		return sha1('file:' . $filename . "\n" . 'cache_id:' . $cache_id . "\n" . 'compile_id:' . $compile_id);
	}
/**
*	Attempts to validate the specified argument, throwing an {@link PDO_doc_cache_Exception Exception} if validation fails
*	@param mixed $arg the argument to validate
*	@param string $is informs {@link PDO_doc_cache::validate_arg() }what the variable is in order to aid validation
*	@uses PDO_doc_cache_Exception
*/
	protected static function validate_arg($arg,$is)
	{
		switch($is)
		{
			case 'filename':
				if(is_string($arg) === false)
				{
					throw new PDO_doc_cache_Exception('Filename must be given as string, \'' . gettype($arg) . '\' given.',103);
				}
			break;
			case 'cache_id':
			case 'compile_id':
				if(is_string($arg) === false && is_integer($arg) === false && is_float($arg) === false)
				{
					throw new PDO_doc_cache_Exception($is . ' must be given as string, integer or float, \'' . gettype($arg) . '\' given.',103);
				}
			break;
			case 'lifetime':
			case 'expire_time':
				if(is_integer($arg) === false)
				{
					throw new PDO_doc_cache_Exception($is . ' must be given as integer, \'' . gettype($arg) . '\' given.',103);
				}
			break;
			default:
				throw new PDO_doc_cache_Exception('Could not validate \'' . (string)$is . '\', no validation scheme registered.',103);
			break;
		}
	}
/**
*	Validates the filename
*	@param string|mixed $filename filename to validate
*	@uses PDO_doc_cache::validate_arg();
*/
	final public static function validate_filename($filename)
	{
		self::validate_arg($filename,'filename');
	}
/**
*	Validates the cache ID
*	@param string|mixed $cache_id cache ID to validate
*	@uses PDO_doc_cache::validate_arg();
*/
	final public static function validate_cache_id($cache_id)
	{
		self::validate_arg($cache_id,'cache_id');
	}
/**
*	Validates the compile ID
*	@param string|mixed $compile_id compile ID to validate
*	@uses PDO_doc_cache::validate_arg();
*/
	final public static function validate_compile_id($compile_id)
	{
		self::validate_arg($compile_id,'compile_id');
	}
/**
*	Validates the lifetime
*	@param integer|mixed $lifetime lifetime to validate
*	@uses PDO_doc_cache::validate_arg();
*/
	final public static function validate_lifetime($lifetime)
	{
		self::validate_arg($lifetime,'lifetime');
	}
/**
*	Uses {@link http://uk.php.net/manual/en/language.oop5.reflection.php#language.oop5.reflection.reflectionclass Reflection} to automagically assign the prepared statement to the correct object property
*	@param PDO_doc_cache $PDO_doc_cache the instance of {@link PDO_doc_cache} to which to set properties on
*	@param string $operation Used by reflection to get the constant containing the SQL query and set the property for the PDOStatement handler.
*	@uses PDO_doc_cache_Exception
*	@return PDOStatement
*/
	protected static function prepare_sth(PDO_doc_cache $PDO_doc_cache,$operation)
	{
		if(is_string($operation) === false)
		{
			throw new PDO_doc_cache_Exception('Operation must be specified as string.',104);
		}
		static $Reflections = array();
		$c = get_class($PDO_doc_cache);
		if(isset($Reflections[$c]) === false)
		{
			$Reflections[$c] = new ReflectionClass($c);
		}
		$constants = $Reflections[$c]->getConstants();
		$constant_to_use = 'sql_' . $operation;
		if(array_key_exists($constant_to_use,$constants))
		{
			$property = 'sth_' . $operation;
			if(isset($PDO_doc_cache->$property) === false)
			{
				$PDO_doc_cache->$property = $PDO_doc_cache->PDO->prepare($constants[$constant_to_use]);
			}
			return $PDO_doc_cache->$property;
		}
		else
		{
			throw new PDO_doc_cache_Exception('Unsupported operation: ' . $operation,105);
		}
	}
/**
*	For use with {@link http://www.smarty.net/manual/en/section.template.cache.handler.func.php Smarty's cache handler}
*	<code>
*	<?php
*	#	$Smarty is an instance of Smarty
*	#	$PDO_doc_cache is an instance of PDO_doc_cache
*		$Smarty->cache_handler_func = array(
*			$PDO_doc_cache,
*			'Smarty_cache_handler'
*		);
*	?>
*	</code>
*/
	public function Smarty_cache_handler($action, & $Smarty, & $cache_content, $filename = null, $cache_id = null, $compile_id = null, $expire_time = null)
	{
		$return = false;
		try
		{
			switch($action)
			{
				case 'read':
					$doc = $this->fetch($filename,$cache_id,$compile_id);
					if($doc === false)
					{
						$return = false;
					}
					else
					{
						$cache_content = $doc;
						$return = $cache_content;
					}
				break;
				case 'write':
					$doc = $this->cache($filename,$cache_content,$Smarty->cache_lifetime,$cache_id,$compile_id);
					if($doc === false)
					{
						$Smarty->trigger_error('cache_handler: could not write to cache');
						$return = false;
					}
					else
					{
						$return = $doc;
					}
				break;
				case 'clear':
					$return = $this->clear_cache($filename,$cache_id,$compile_id,$expire_time);
				break;
				default:
					$Smarty->trigger_error('cache_handler: unsupported action \'' . $action . '\'.');
					$return = false;
				break;
			}
		}
		catch(PDO_doc_cache_Exception $e)
		{
			$Smarty->trigger_error('cache_handler: ' . (string)$e);
			$return = false;
		}
		return $return;
	}

/**
*	Enables auto-serialization of document contents
*	@uses PDO_doc_cache::$auto_serialize
*/
	public function auto_serialize_on()
	{
		$this->auto_serialize = true;
	}
/**
*	Disables auto-serialization of document contents
*	@uses PDO_doc_cache::$auto_serialize
*/
	public static function auto_serialize_off()
	{
		$this->auto_serialize = false;
	}
/**
*	Sets the callback functions used by PDO_doc_cache
*	@param callback|null $cache_callback Sets the callback function to be used when data is stored in the database
*	@uses PDO_doc_cache::$cache_callback
*	@see PDO_doc_cache::cache();
*	@param callback|null $fetch_callback Sets the callback function to be used when data is retrieved from the database
*	@uses PDO_doc_cache::$fetch_callback
*	@see PDO_doc_cache::fetch()
*/
	public function set_callback($cache_callback,$fetch_callback)
	{
		$this->cache_callback = $cache_callback;
		$this->fetch_callback = $fetch_callback;
	}

/**
*	Checks if the given document is cached
*	@param string $filename
*	@uses PDO_doc_cache::validate_filename()
*	@param string $cache_id
*	@uses PDO_doc_cache::validate_cache_id()
*	@param string $compile_id
*	@uses PDO_doc_cache::validate_compile_id()
*	@uses PDO_doc_cache::fetch()
*	@return bool TRUE if cached, FALSE otherwise
*/
	public function is_cached($filename,$cache_id='',$compile_id='')
	{
		self::validate_filename($filename);
		self::validate_cache_id($cache_id);
		self::validate_compile_id($compile_id);
		if($this->fetch($filename,$cache_id,$compile_id) === false)
		{
			return false;
		}
		else
		{
			$expires = $this->expires($filename,$cache_id,$compile_id);
			return ($expires !== false && $expires > time());
		}
	}
/**
*	Fetches the specified document from the database
*	@param string $filename
*	@uses PDO_doc_cache::validate_filename()
*	@param string $cache_id
*	@uses PDO_doc_cache::validate_cache_id()
*	@param string $compile_id
*	@uses PDO_doc_cache::validate_compile_id()
*	@uses PDO_doc_cache::prepare_sth()
*	@uses PDO_doc_cache_Exception
*	@uses PDO_doc_cache::$sth_get_document
*	@uses PDO_doc_cache::$auto_serialize
*	@uses PDO_doc_cache::$fetch_callback
*	@return mixed Returns the document, FALSE on fail
*/
	public function fetch($filename,$cache_id='',$compile_id='')
	{
		self::validate_filename($filename);
		self::validate_cache_id($cache_id);
		self::validate_compile_id($compile_id);
		$document = null;
		if(isset($this->sth_get_document) === false)
		{
			try
			{
				self::prepare_sth($this,'get_document');
			}
			catch(PDOException $e)
			{
				throw new PDO_doc_cache_Exception(self::error_string_PDO_could_not_prepare_statement,300);
			}
		}
		try
		{
			$this->sth_get_document->bindValue(':filename',$filename,PDO::PARAM_STR);
			$this->sth_get_document->bindValue(':cache_id',$cache_id,PDO::PARAM_STR);
			$this->sth_get_document->bindValue(':compile_id',$compile_id,PDO::PARAM_STR);
		}
		catch(PDOException $e)
		{
			throw new PDO_doc_cache_Exception(self::error_string_PDO_could_not_bind_parameters,301);
		}
		try
		{
			$this->sth_get_document->bindColumn('document',$document,PDO::PARAM_STR);
		}
		catch(PDOException $e)
		{
			throw new PDO_doc_cache_Exception(self::error_string_PDO_could_not_bind_column,302);
		}
		try
		{
			$this->sth_get_document->execute();
		}
		catch(PDOException $e)
		{
			throw new PDO_doc_cache_Exception(self::error_string_PDO_could_not_execute_statement,303);
		}
		try
		{
			$this->sth_get_document->fetch(PDO::FETCH_BOUND);
		}
		catch(PDOException $e)
		{
			throw new PDO_doc_cache_Exception(self::error_string_PDO_could_not_fetch_bound_col,304);
		}
		if(is_null($document))
		{
			$document = false;
		}
		else
		{
			if(isset($this->fetch_callback))
			{
				$document = call_user_func_array($this->fetch_callback,array($document));
			}
			if($this->auto_serialize)
			{
				$document = unserialize($document);
			}
		}
		return $document;
	}
/**
*	Stores the specified document in the database
*	@param string $filename
*	@uses PDO_doc_cache::validate_filename()
*	@param string $cache_id
*	@uses PDO_doc_cache::validate_cache_id()
*	@param string $compile_id
*	@uses PDO_doc_cache::validate_compile_id()
*	@uses PDO_doc_cache::prepare_sth()
*	@uses PDO_doc_cache_Exception
*	@uses PDO_doc_cache::$sth_cache_document
*	@uses PDO_doc_cache::$auto_serialize
*	@uses PDO_doc_cache::$fetch_callback
*	@return bool
*/
	public function cache($filename,$document,$lifetime=30,$cache_id='',$compile_id='')
	{
		self::validate_filename($filename);
		self::validate_cache_id($cache_id);
		self::validate_compile_id($compile_id);
		self::validate_lifetime($lifetime);
		if(isset($this->sth_cache_document) === false)
		{
			try
			{
				self::prepare_sth($this,'cache_document');
			}
			catch(PDOException $e)
			{
				throw new PDO_doc_cache_Exception(self::error_string_PDO_could_not_prepare_statement,400);
			}
		}
		if($this->auto_serialize)
		{
			$document = serialize($document);
		}
		if(isset($this->cache_callback))
		{
			$document = call_user_func_array($this->cache_callback,array($document));
		}
		try
		{
			$this->sth_cache_document->bindValue(':id',self::row_id($filename,$cache_id,$compile_id),PDO::PARAM_STR);
			$this->sth_cache_document->bindParam(':filename',$filename,PDO::PARAM_STR);
			$this->sth_cache_document->bindParam(':cache_id',$cache_id,PDO::PARAM_STR);
			$this->sth_cache_document->bindParam(':compile_id',$compile_id,PDO::PARAM_STR);
			$this->sth_cache_document->bindParam(':lifetime',$lifetime,PDO::PARAM_INT);
			$this->sth_cache_document->bindParam(':document',$document,PDO::PARAM_STR);
			$this->sth_cache_document->bindValue(':created',time(),PDO::PARAM_INT);
		}
		catch(PDOException $e)
		{
			throw new PDO_doc_cache_Exception(self::error_string_PDO_could_not_bind_parameters,401);
		}
		try
		{
			return $this->sth_cache_document->execute();
		}
		catch(PDOException $e)
		{
			throw new PDO_doc_cache_Exception(self::error_string_PDO_could_not_execute_statement,403);
		}
	}
/**
*	Fetches the expiry for the specified document
*	@param string $filename
*	@uses PDO_doc_cache::validate_filename()
*	@param string $cache_id
*	@uses PDO_doc_cache::validate_cache_id()
*	@param string $compile_id
*	@uses PDO_doc_cache::validate_compile_id()
*	@uses PDO_doc_cache::prepare_sth()
*	@uses PDO_doc_cache_Exception
*	@uses PDO_doc_cache::$sth_get_expires
*	@return integer|bool Returns the expiry time as a UNIX tiemstamp, FALSE on fail
*/
	public function expires($filename,$cache_id='',$compile_id='')
	{
		self::validate_filename($filename);
		self::validate_cache_id($cache_id);
		self::validate_compile_id($compile_id);
		$expires = false;
		if(isset($this->sth_get_expires) === false)
		{
			try
			{
				self::prepare_sth($this,'get_expires');
			}
			catch(PDOException $e)
			{
				throw new PDO_doc_cache_Exception(self::error_string_PDO_could_not_prepare_statement,200);
			}
		}
		try
		{
			$this->sth_get_expires->bindParam(':filename',$filename,PDO::PARAM_STR);
			$this->sth_get_expires->bindParam(':cache_id',$cache_id,PDO::PARAM_STR);
			$this->sth_get_expires->bindParam(':compile_id',$compile_id,PDO::PARAM_STR);
		}
		catch(PDOException $e)
		{
			throw new PDO_doc_cache_Exception(self::error_string_PDO_could_not_bind_parameters,201);
		}
		try
		{
			$this->sth_get_expires->bindColumn('expires',$expires,PDO::PARAM_INT);
		}
		catch(PDOException $e)
		{
			throw new PDO_doc_cache_Exception(self::error_string_PDO_could_not_bind_column,202);
		}
		try
		{
			$this->sth_get_expires->execute();
		}
		catch(PDOException $e)
		{
			throw new PDO_doc_cache_Exception(self::error_string_PDO_could_not_execute_statement,203);
		}
		try
		{
			$fetch_action = $this->sth_get_expires->fetch(PDO::FETCH_BOUND);
		}
		catch(PDOException $e)
		{
			throw new PDO_doc_cache_Exception(self::error_string_PDO_could_not_fetch_bound_col,204);
		}
		return ($fetch_action === false) ? false : $expires;
	}
/**
*	Fetches the creation for the specified document
*	@param string $filename
*	@uses PDO_doc_cache::validate_filename()
*	@param string $cache_id
*	@uses PDO_doc_cache::validate_cache_id()
*	@param string $compile_id
*	@uses PDO_doc_cache::validate_compile_id()
*	@uses PDO_doc_cache::prepare_sth()
*	@uses PDO_doc_cache_Exception
*	@uses PDO_doc_cache::$sth_get_created
*	@return integer|bool Returns the creation time as a UNIX tiemstamp, FALSE on fail
*/
	public function created($filename,$cache_id='',$compile_id='')
	{
		self::validate_filename($filename);
		self::validate_cache_id($cache_id);
		self::validate_compile_id($compile_id);
		$created = false;
		if(isset($this->sth_get_created) === false)
		{
			try
			{
				self::prepare_sth($this,'get_created');
			}
			catch(PDOException $e)
			{
				throw new PDO_doc_cache_Exception(self::error_string_PDO_could_not_prepare_statement,600);
			}
		}
		try
		{
			$this->sth_get_created->bindParam(':filename',$filename,PDO::PARAM_STR);
			$this->sth_get_created->bindParam(':cache_id',$cache_id,PDO::PARAM_STR);
			$this->sth_get_created->bindParam(':compile_id',$compile_id,PDO::PARAM_STR);
		}
		catch(PDOException $e)
		{
			throw new PDO_doc_cache_Exception(self::error_string_PDO_could_not_bind_parameters,601);
		}
		try
		{
			$this->sth_get_created->bindColumn('created',$created,PDO::PARAM_INT);
		}
		catch(PDOException $e)
		{
			throw new PDO_doc_cache_Exception(self::error_string_PDO_could_not_bind_column,602);
		}
		try
		{
			$this->sth_get_created->execute();
		}
		catch(PDOException $e)
		{
			throw new PDO_doc_cache_Exception(self::error_string_PDO_could_not_execute_statement,603);
		}
		try
		{
			$fetch_action = $this->sth_get_created->fetch(PDO::FETCH_BOUND);
		}
		catch(PDOException $e)
		{
			throw new PDO_doc_cache_Exception(self::error_string_PDO_could_not_fetch_bound_col,604);
		}
		return ($fetch_action === false) ? false : $created;
	}
/**
*	Runs garbage collection on the database cache
*	@uses PDO_doc_cache::prepare_sth()
*	@uses PDO_doc_cache_Exception
*	@uses PDO_doc_cache::$sth_gc_cache
*/
	protected function gc_cache()
	{
		if(isset($this->sth_gc_cache) === false)
		{
			try
			{
				self::prepare_sth($this,'gc_cache');
			}
			catch(PDOException $e)
			{
				throw new PDO_doc_cache_Exception(self::error_string_PDO_could_not_prepare_statement,500);
			}
		}
		try
		{
			$this->sth_gc_cache->bindValue(':now',time(),PDO::PARAM_INT);
		}
		catch(PDOException $e)
		{
			throw new PDO_doc_cache_Exception(self::error_string_PDO_could_not_bind_parameters,501);
		}
		try
		{
			$this->sth_gc_cache->execute();
		}
		catch(PDOException $e)
		{
			throw new PDO_doc_cache_Exception(self::error_string_PDO_could_not_execute_statement,503);
		}
	}
/**
*	Attempts to clear all documents over a specified age
*	@param integer $expire_time
*	@uses PDO_doc_cache::validate_arg()
*	@uses PDO_doc_cache::prepare_sth()
*	@uses PDO_doc_cache::$sth_clear_all_cache
*	@uses PDO_doc_cache_Exception
*/
	public function clear_all_cache($expire_time = 0)
	{
		self::validate_arg($expire_time,'expire_time');
		$sth = null;
		try
		{
			$sth = self::prepare_sth($this,'clear_all_cache');
		}
		catch(PDOException $e)
		{
			throw new PDO_doc_cache_Exception(self::error_string_PDO_could_not_prepare_statement,600);
		}
		try
		{
			$this->sth_clear_all_cache->bindValue(':now',time(),PDO::PARAM_INT);
			$this->sth_clear_all_cache->bindValue(':expire_time',$expire_time,PDO::PARAM_INT);
		}
		catch(PDOException $e)
		{
			throw new PDO_doc_cache_Exception(self::error_string_PDO_could_not_bind_parameters,601);
		}
		try
		{
			$sth->execute();
		}
		catch(PDOException $e)
		{
			throw new PDO_doc_cache_Exception(self::error_string_PDO_could_not_execute_statement,603);
		}
	}
/**
*	Attempts to clear all documents over a specified age
*	@param string $filename
*	@uses PDO_doc_cache::validate_filename()
*	@param string $cache_id
*	@uses PDO_doc_cache::validate_cache_id()
*	@param string $compile_id
*	@uses PDO_doc_cache::validate_compile_id()
*	@param integer $expire_time
*	@uses PDO_doc_cache::validate_arg()
*	@uses PDO_doc_cache::prepare_sth()
*	@uses PDO_doc_cache::$sth_clear_cache_filename
*	@uses PDO_doc_cache::$sth_clear_cache_filename_cache_id
*	@uses PDO_doc_cache::$sth_clear_cache_filename_compile_id
*	@uses PDO_doc_cache::$sth_clear_cache_filename_cache_id_compile_id
*	@uses PDO_doc_cache::$sth_clear_cache_cache_id
*	@uses PDO_doc_cache::$sth_clear_cache_cache_id_compile_id
*	@uses PDO_doc_cache::$sth_clear_cache_compile_id
*	@uses PDO_doc_cache_Exception
*/
	public function clear_cache($filename=null,$cache_id=null,$compile_id=null,$expire_time=0)
	{
		self::validate_arg($expire_time,'expire_time');
		if(is_null($filename) === false)
		{
			self::validate_filename($filename);
		}
		if(isset($cache_id))
		{
			self::validate_cache_id($cache_id);
		}
		if(isset($compile_id))
		{
			self::validate_compile_id($compile_id);
		}
		$sth = null;
		try
		{
			if(isset($filename,$cache_id,$compile_id))
			{
				$operation = 'clear_cache_filename_cache_id_compile_id';
			}
			else if(isset($filename,$cache_id))
			{
				$operation = 'clear_cache_filename_cache_id';
			}
			else if(isset($filename,$compile_id))
			{
				$operation = 'clear_cache_filename_compile_id';
			}
			else if(isset($filename))
			{
				$operation = 'clear_cache_filename';
			}
			else if(isset($cache_id,$compile_id))
			{
				$operation = 'clear_cache_cache_id_compile_id';
			}
			else if(isset($compile_id))
			{
				$operation = 'clear_cache_compile_id';
			}
			else if(isset($cache_id))
			{
				$operation = 'clear_cache_cache_id';
			}
			else
			{
				$operation = 'all aboard the fail boat';
			}
			
			$sth = self::prepare_sth($this,$operation);

		}
		catch(PDOException $e)
		{
			throw new PDO_doc_cache_Exception(self::error_string_PDO_could_not_prepare_statement,700);
		}
		try
		{
			if(isset($cache_id))
			{
				$sth->bindValue(':cache_id',$cache_id,PDO::PARAM_STR);
			}
			if(isset($compile_id))
			{
				$sth->bindValue(':compile_id',$compile_id,PDO::PARAM_STR);
			}
			if(isset($filename))
			{
				$sth->bindValue(':filename',$filename,PDO::PARAM_STR);
			}
			$sth->bindValue(':now',time(),PDO::PARAM_INT);
			$sth->bindValue(':expire_time',$expire_time,PDO::PARAM_INT);
		}
		catch(PDOException $e)
		{
			throw new PDO_doc_cache_Exception(self::error_string_PDO_could_not_bind_parameters,701);
		}
		try
		{
			$sth->execute();
		}
		catch(PDOException $e)
		{
			throw new PDO_doc_cache_Exception(self::error_string_PDO_could_not_execute_statement,703);
		}
	}
}

/**
*	Acts as a document-specific wrapper to {@link PDO_doc_cache}
*	@package PDO_doc_cache
*	@subpackage PDO_doc_cache_document
**/
class PDO_doc_cache_document
{
/**
*	@var PDO_doc_cache The PDO_doc_cache instance used by the document
*/
	protected $PDO_doc_cache;
/**
*	@var string
*/
	protected $filename;
/**
*	@var string
*/
	protected $cache_id;
/**
*	@var string
*/
	protected $compile_id;
/**
*	@var mixed
*/
	protected $document;
/**
*	@var integer
*/
	protected $expires;
/**
*	@var created
*/
	protected $created;

/**
*	@see PDO_doc_cache::set_callback()
*/
	public function set_callback($cache_callback,$fetch_callback)
	{
		$this->PDO_doc_cache->set_callback($cache_callback,$fetch_callback);
	}
/**
*	@see PDO_doc_cache::auto_serialize_on()
*/
	public function auto_serialize_on()
	{
		$this->PDO_doc_cache->auto_serialize_on();
	}
/**
*	@see PDO_doc_cache::auto_serialize_off)
*/
	public function auto_serialize_off()
	{
		$this->PDO_doc_cache->auto_serialize_off();
	}

/**
*	@param PDO_doc_cache $PDO_doc_cache
*	@uses PDO_doc_cache_document::$PDO_doc_cache
*	@param string $filename
*	@uses PDO_doc_cache::validate_filename()
*	@uses PDO_doc_cache_document::$filename
*	@param string $cache_id
*	@uses PDO_doc_cache::validate_cache_id)
*	@uses PDO_doc_cache_document::$cache_id
*	@param string $compile_id
*	@uses PDO_doc_cache::validate_compile_id)
*	@uses PDO_doc_cache_document::$compile_id
*/
	public function __construct(PDO_doc_cache $PDO_doc_cache,$filename,$cache_id='',$compile_id='')
	{
		$this->PDO_doc_cache = $PDO_doc_cache;
		PDO_doc_cache::validate_filename($filename);
		PDO_doc_cache::validate_cache_id($cache_id);
		PDO_doc_cache::validate_compile_id($compile_id);
		$this->filename = $filename;
		$this->cache_id = $cache_id;
		$this->compile_id = $compile_id;
	}
/**
*	@see PDO_doc_cache::is_cached()
*/
	public function is_cached()
	{
		return $this->PDO_doc_cache->is_cached($this->filename,$this->cache_id,$this->compile_id);
	}
/**
*	@see PDO_doc_cache::fetch)
*/
	public function fetch()
	{
		$doc = $this->PDO_doc_cache->fetch($this->filename,$this->cache_id,$this->compile_id);
		if($doc === false)
		{
			return false;
		}
		if($this->document !== $doc)
		{
			$this->expires = null;
			$this->expires();
		}
		$this->document = $doc;
		return $this->document;
	}
/**
*	@see PDO_doc_cache::expires()
*/
	public function expires()
	{
		if(isset($this->expires) === false)
		{
			$this->expires = $this->PDO_doc_cache->expires($this->filename,$this->cache_id,$this->compile_id);
		}
		return $this->expires;
	}
/**
*	@see PDO_doc_cache::created()
*/
	public function created()
	{
		if(isset($this->created) === false)
		{
			$this->created = $this->PDO_doc_cache->created($this->filename,$this->cache_id,$this->compile_id);
		}
		return $this->created;
	}
/**
*	@see PDO_doc_cache::cache()
*/
	public function cache($document,$lifetime)
	{
		PDO_doc_cache::validate_lifetime($lifetime);
		return $this->PDO_doc_cache->cache($this->filename,$document,$lifetime,$this->cache_id,$this->compile_id);
	}
/**
*	@see PDO_doc_cache::clear_cache()
*/
	public function clear_cache($expire_time=0)
	{
		$this->PDO_doc_cache->clear_cache($this->filename,$this->cache_id,$this->compile_id,$expire_time);
	}
}

/**
*	Automatically compresses/decompresses document
*	@package PDO_doc_cache_document
**/
class PDO_doc_cache_document_deflate extends PDO_doc_cache_document
{
/**
*	@see PDO_doc_cache_document::__construct
*	@see PDO_doc_cache::set_callback()
*/
	public function __construct(PDO_doc_cache $PDO_doc_cache,$filename,$cache_id='',$compile_id='')
	{
		$PDO_doc_cache->set_callback(
			create_function('$document','return gzcompress($document,9);'),
			create_function('$document','return gzuncompress($document);')
		);
		parent::__construct($PDO_doc_cache,$filename,$cache_id,$compile_id);
	}
}
?>